/* picoc variable storage. This provides ways of defining and accessing
 * variables */

#include "variable.h"
#include "table.h"
#include "type.h"
#include "parse.h"
#include "platform.h"
/* maximum size of a Value to temporarily copy while we create a variable */
#define MAX_TMP_COPY_BUF (256)

Variable::Variable() {
    //this->pc = pc;
}

void Variable::VariableInit(Interpreter *pc) {
    MyTable::TableInitTable(&(pc->globalTable), &(pc->globalHashTable)[0],
        GLOBAL_TABLE_SIZE, true);
    MyTable::TableInitTable(&pc->stringLiteralTable, &pc->stringLiteralHashTable[0],
        STRING_LITERAL_TABLE_SIZE, true);
    pc->topStackFrame = NULL;
}

void Variable::VariableFree(Interpreter *pc, Value *val) {
    if (val->valOnHeap || val->anyValOnHeap) {
        /* free function bodies */
        if (val->typ == &pc->functionType &&
                val->val->FuncDef.intrinsic == NULL &&
                val->val->FuncDef.body.pos != NULL)
            Interpreter::HeapFreeMem((void*)val->val->FuncDef.body.pos);

        /* free macro bodies */
        if (val->typ == &pc->macroType)
            Interpreter::HeapFreeMem((void*)val->val->MacroDef.body.pos);

        /* free the AnyValue */
        if (val->anyValOnHeap)
            Interpreter::HeapFreeMem(val->val);
    }

    /* free the Value */
    if (val->valOnHeap)
        Interpreter::HeapFreeMem(val);
}

void Variable::VariableTableCleanup(Interpreter *pc, Table *hashTable) {
    TableEntry *entry;
    TableEntry *nextEntry;

    for (int i = 0; i < hashTable->size; i++) {
        for (entry = hashTable->hashTable[i];
                entry != NULL;
                entry = nextEntry) {
            nextEntry = entry->next;
            VariableFree(pc, entry->p.v.val);

            /* free the hash table entry */
            Interpreter::HeapFreeMem(entry);
        }
    }
}

void Variable::VariableCleanup(Interpreter *pc) {
    VariableTableCleanup(pc, &pc->globalTable);
    VariableTableCleanup(pc, &pc->stringLiteralTable);
}

void *Variable::VariableAlloc(Interpreter *pc, ParseState *parser, int size, int onHeap) {
    void *newValue;

    if (onHeap)
        newValue = Interpreter::HeapAllocMem(size);
    else
        newValue = Interpreter::HeapAllocStack(size);

    if (newValue == NULL)
        Platform::ProgramFail(parser, "(VariableAlloc) out of memory");

#ifdef DEBUG_HEAP
    if (!onHeap)
        printf("pushing %d at 0x%lx\n", size, (unsigned long)newValue);
#endif

    return newValue;
}

Value *Variable::VariableAllocValueAndData(Interpreter *pc, ParseState *parser, int dataSize,
    int isLValue, Value *lValueFrom, int onHeap) {
    Value *newValue = (Value *)VariableAlloc(pc, parser, MEM_ALIGN(sizeof(Value)) + dataSize,
        onHeap);
    newValue->val = (AnyValue*)((char*)newValue +
        MEM_ALIGN(sizeof(Value)));
    newValue->valOnHeap = onHeap;
    newValue->anyValOnHeap = false;
    newValue->valOnStack = !onHeap;
    newValue->isLValue = isLValue;
    newValue->lValueFrom = lValueFrom;
    if (parser)
        newValue->scopeID = parser->scopeID;

    newValue->outOfScope = false;

    return newValue;
}

Value *Variable::VariableAllocValueFromType(Interpreter *pc, ParseState *parser, ValueType *typ,
    int isLValue, Value *lValueFrom, int onHeap) {
    int size = Type::TypeSize(typ, typ->arraySize, false);
    Value *newValue = VariableAllocValueAndData(pc, parser, size, isLValue,
        lValueFrom, onHeap);
    assert(size >= 0 || typ == &pc->voidType);
    newValue->typ = typ;

    return newValue;
}

Value *Variable::VariableAllocValueAndCopy(Interpreter *pc, ParseState *parser, Value *fromValue,
    int onHeap) {
    int copySize = Type::TypeSizeValue(fromValue, true);
    char tmpBuf[MAX_TMP_COPY_BUF];
    ValueType *dType = fromValue->typ;
    Value *newValue;

    assert(copySize <= MAX_TMP_COPY_BUF);
    memcpy((void*)&tmpBuf[0], (void*)fromValue->val, copySize);
    newValue = VariableAllocValueAndData(pc, parser, copySize, fromValue->isLValue,
        fromValue->lValueFrom, onHeap);
    newValue->typ = dType;
    memcpy((void*)newValue->val, (void*)&tmpBuf[0], copySize);

    return newValue;
}

Value *Variable::VariableAllocValueFromExistingData(Interpreter *pc, ParseState *parser,
    ValueType *typ, AnyValue *fromValue, int isLValue,
    Value *lValueFrom) {
    Value *newValue = (Value *)VariableAlloc(pc, parser, sizeof(Value),
        false);
    newValue->typ = typ;
    newValue->val = fromValue;
    newValue->valOnHeap = false;
    newValue->anyValOnHeap = false;
    newValue->valOnStack = false;
    newValue->isLValue = isLValue;
    newValue->lValueFrom = lValueFrom;

    return newValue;
}

Value *Variable::VariableAllocValueShared(Interpreter *pc, ParseState *parser,
    Value *fromValue) {
    return VariableAllocValueFromExistingData(pc, parser, fromValue->typ,
        fromValue->val, fromValue->isLValue,
        fromValue->isLValue ? fromValue : NULL);
}

void Variable::VariableRealloc(Interpreter *pc, ParseState *parser, Value *fromValue,
    int newSize) {
    if (fromValue->anyValOnHeap)
        Interpreter::HeapFreeMem(fromValue->val);

    fromValue->val = (AnyValue *)VariableAlloc(pc, parser, newSize, true);
    fromValue->anyValOnHeap = true;
}

int Variable::VariableScopeBegin(ParseState *parser, int* oldScopeID) {
    int count;
    TableEntry *entry;
    TableEntry *nextEntry;
#ifdef DEBUG_VAR_SCOPE
    int FirstPrint = 0;
#endif

    if (parser->scopeID == -1)
        return -1;

    Table *hashTable = (parser->pc->topStackFrame == NULL) ?
        &(parser->pc->globalTable) : &(parser->pc->topStackFrame)->localTable;

    /* XXX dumb hash, let's hope for no collisions... */
    *oldScopeID = parser->scopeID;
    parser->scopeID = (int)(intptr_t)(parser->sourceText) *
        ((int)(intptr_t)(parser->pos) / sizeof(char*));
    /* or maybe a more human-readable hash for debugging? */
    /* parser->scopeID = parser->line * 0x10000 + parser->characterPos; */

    for (count = 0; count < hashTable->size; count++) {
        for (entry = hashTable->hashTable[count];
                entry != NULL; entry = nextEntry) {
            nextEntry = entry->next;
            if (entry->p.v.val->scopeID == parser->scopeID &&
                    entry->p.v.val->outOfScope == true) {
                entry->p.v.val->outOfScope = false;
                entry->p.v.key = (char*)((intptr_t)entry->p.v.key & ~1);
#ifdef DEBUG_VAR_SCOPE
                if (!FirstPrint) PRINT_SOURCE_POS();
                FirstPrint = 1;
                printf(">>> back into scope: %s %x %d\n", entry->p.v.key,
                    entry->p.v.val->scopeID, entry->p.v.val->val->integer);
#endif
            }
        }
    }

    return parser->scopeID;
}

void Variable::VariableScopeEnd(ParseState *parser, int scopeID, int prevScopeID) {
    int count;
    TableEntry *entry;
    TableEntry *nextEntry = NULL;
#ifdef DEBUG_VAR_SCOPE
    int FirstPrint = 0;
#endif

    if (scopeID == -1)
        return;

    Table *hashTable = (parser->pc->topStackFrame == NULL) ?
        &(parser->pc->globalTable) : &(parser->pc->topStackFrame)->localTable;

    for (count = 0; count < hashTable->size; count++) {
        for (entry = hashTable->hashTable[count]; entry != NULL;
            entry = nextEntry) {
            nextEntry = entry->next;
            if ((entry->p.v.val->scopeID == scopeID) &&
                    (entry->p.v.val->outOfScope == false)) {
#ifdef DEBUG_VAR_SCOPE
                if (!FirstPrint) PRINT_SOURCE_POS();
                FirstPrint = 1;
                printf(">>> out of scope: %s %x %d\n", entry->p.v.key,
                    entry->p.v.val->scopeID, entry->p.v.val->val->integer);
#endif
                entry->p.v.val->outOfScope = true;
                entry->p.v.key = (char*)((intptr_t)entry->p.v.key | 1); /* alter the key so it won't be found by normal searches */
            }
        }
    }

    parser->scopeID = prevScopeID;
}

int Variable::VariableDefinedAndOutOfScope(Interpreter *pc, const char* ident) {
    int count;
    TableEntry *entry;

    Table * hashTable = (pc->topStackFrame == NULL) ?
        &(pc->globalTable) : &(pc->topStackFrame)->localTable;

    for (count = 0; count < hashTable->size; count++) {
        for (entry = hashTable->hashTable[count]; entry != NULL;
            entry = entry->next) {
            if (entry->p.v.val->outOfScope == true &&
                    (char*)((intptr_t)entry->p.v.key & ~1) == ident)
                return true;
        }
    }
    return false;
}

Value *Variable::VariableDefine(Interpreter *pc, ParseState *parser, char *ident, Value *initValue,
    ValueType *typ, int makeWritable) {
    int scopeID = parser ? parser->scopeID : -1;
    Value * assignValue;
    Table * currentTable = (pc->topStackFrame == NULL) ?
        &(pc->globalTable) : &(pc->topStackFrame)->localTable;

#ifdef DEBUG_VAR_SCOPE
    if (parser) fprintf(stderr, "def %s %x (%s:%d:%d)\n", ident, scopeID,
        parser->fileName, parser->line, parser->characterPos);
#endif

    if (initValue != NULL)
        assignValue = VariableAllocValueAndCopy(pc, parser, initValue, pc->topStackFrame == NULL);
    else
        assignValue = VariableAllocValueFromType(pc, parser, typ, makeWritable, NULL,
            pc->topStackFrame == NULL);

    assignValue->isLValue = makeWritable;
    assignValue->scopeID = scopeID;
    assignValue->outOfScope = false;

    if (!MyTable::TableSet(pc, currentTable, ident, assignValue, parser ?
            ((char*)parser->fileName) : NULL, parser ? parser->line : 0,
            parser ? parser->characterPos : 0))
        Platform::ProgramFail(parser, "'%s' is already defined", ident);

    return assignValue;
}

Value *Variable::VariableDefineButIgnoreIdentical(ParseState *parser,
    char *ident, ValueType *typ, int isStatic, int *firstVisit) {
    int declLine;
    int declColumn;
    const char *declFileName;
    Interpreter *pc = parser->pc;
    Value *existingValue;

    /* is the type a forward declaration? */
    if (Type::TypeIsForwardDeclared(parser, typ))
        Platform::ProgramFail(parser, "type '%t' isn't defined", typ);

    if (isStatic) {
        char MangledName[LINEBUFFER_MAX];
        char *MNPos = &MangledName[0];
        char *MNEnd = &MangledName[LINEBUFFER_MAX-1];
        const char *RegisteredMangledName;

        /* make the mangled static name (avoiding using sprintf()
            to minimise library impact) */
        memset((void*)&MangledName, '\0', sizeof(MangledName));
        *MNPos++ = '/';
        strncpy(MNPos, (char*)parser->fileName, MNEnd - MNPos);
        MNPos += strlen(MNPos);

        if (pc->topStackFrame != NULL) {
            /* we're inside a function */
            if (MNEnd - MNPos > 0)
                *MNPos++ = '/';
            strncpy(MNPos, (char*)pc->topStackFrame->funcName, MNEnd - MNPos);
            MNPos += strlen(MNPos);
        }

        if (MNEnd - MNPos > 0) *MNPos++ = '/';
        strncpy(MNPos, ident, MNEnd - MNPos);
        RegisteredMangledName = MyTable::TableStrRegister(pc, MangledName);

        /* is this static already defined? */
        if (!MyTable::TableGet(&pc->globalTable, RegisteredMangledName, &existingValue,
                &declFileName, &declLine, &declColumn)) {
            /* define the mangled-named static variable store in the global scope */
            existingValue = VariableAllocValueFromType(pc, parser, typ, true,
                NULL, true);
            MyTable::TableSet(pc, &pc->globalTable, (char*)RegisteredMangledName,
                existingValue, (char *)parser->fileName, parser->line,
                parser->characterPos);
            *firstVisit = true;
        }

        /* static variable exists in the global scope - now make a
            mirroring variable in our own scope with the short name */
        VariableDefinePlatformVar(pc, parser, ident, existingValue->typ, existingValue->val,
            true);
        return existingValue;
    } else {
        if (parser->line != 0 && MyTable::TableGet((pc->topStackFrame == NULL) ?
                    &pc->globalTable : &pc->topStackFrame->localTable, ident,
                    &existingValue, &declFileName, &declLine, &declColumn)
                && declFileName == parser->fileName && declLine == parser->line &&
                declColumn == parser->characterPos)
            return existingValue;
        else
            return VariableDefine(pc, parser, ident, NULL, typ, true);
    }
}

int Variable::VariableDefined(Interpreter *pc, const char *ident) {
    Value *foundValue;

    if (pc->topStackFrame == NULL || !MyTable::TableGet(&pc->topStackFrame->localTable,
            ident, &foundValue, NULL, NULL, NULL)) {
        if (!MyTable::TableGet(&pc->globalTable, ident, &foundValue, NULL, NULL, NULL))
            return false;
    }

    return true;
}

void Variable::VariableGet(Interpreter *pc, ParseState *parser, const char *ident, Value **lVal) {
    if (pc->topStackFrame == NULL || !MyTable::TableGet(&pc->topStackFrame->localTable,
            ident, lVal, NULL, NULL, NULL)) {
        if (!MyTable::TableGet(&pc->globalTable, ident, lVal, NULL, NULL, NULL)) {
            if (VariableDefinedAndOutOfScope(pc, ident))
                Platform::ProgramFail(parser, "'%s' is out of scope", ident);
            else
                Platform::ProgramFail(parser, "VariableGet ident: '%s' is undefined", ident);
        }
    }
}

void Variable::VariableDefinePlatformVar(Interpreter *pc, ParseState *parser, char *ident, ValueType *typ,
    AnyValue *fromValue, int isWritable) {
    Value *someValue = VariableAllocValueAndData(pc, NULL, 0, isWritable, NULL,
        true);
    someValue->typ = typ;
    someValue->val = fromValue;

    if (!MyTable::TableSet(pc,
            (pc->topStackFrame == NULL) ? &pc->globalTable : &pc->topStackFrame->localTable,
                           MyTable::TableStrRegister(pc, ident), someValue,
            parser ? parser->fileName : NULL,
            parser ? parser->line : 0, parser ? parser->characterPos : 0))
        Platform::ProgramFail(parser, "'%s' is already defined", ident);
}

void Variable::VariableStackPop(ParseState *parser, Value *var) {
    int success;

#ifdef DEBUG_HEAP
    if (var->valOnStack)
        printf("popping %ld at 0x%lx\n",
            (unsigned long)(sizeof(Value) + TypeSizeValue(var, false)),
            (unsigned long)var);
#endif

    if (var->valOnHeap) {
        if (var->val != NULL)
            Interpreter::HeapFreeMem(var->val);
        success = Interpreter::HeapPopStack(var, sizeof(Value));  /* free from heap */
    } else if (var->valOnStack)
        success = Interpreter::HeapPopStack(var,
        sizeof(Value)+ Type::TypeSizeValue(var, false));  /* free from stack */
    else
        success = Interpreter::HeapPopStack(var, sizeof(Value));  /* Value isn't our problem */

    if (!success)
        Platform::ProgramFail(parser, "stack underrun");
}

void Variable::VariableStackFrameAdd(ParseState *parser, const char *funcName,
    int numParams) {
    stackFrame *newFrame;

    Interpreter::HeapPushStackFrame();
    newFrame = (stackFrame *)Interpreter::HeapAllocStack(sizeof(stackFrame)+sizeof(Value*)*numParams);
    if (newFrame == NULL)
        Platform::ProgramFail(parser, "(VariableStackFrameAdd) out of memory");

    Parse::ParserCopy(&newFrame->returnParser, parser);
    newFrame->funcName = funcName;
    newFrame->parameter = (Value **)((numParams > 0) ?
        ((void*)((char*)newFrame+sizeof(stackFrame))) : NULL);
    MyTable::TableInitTable(&newFrame->localTable, &newFrame->localHashTable[0],
        LOCAL_TABLE_SIZE, false);
    newFrame->previousStackFrame = parser->pc->topStackFrame;
    parser->pc->topStackFrame = newFrame;
}

void Variable::VariableStackFramePop(ParseState *parser) {
    if (parser->pc->topStackFrame == NULL)
        Platform::ProgramFail(parser, "stack is empty - can't go back");

    Parse::ParserCopy(parser, &parser->pc->topStackFrame->returnParser);
    parser->pc->topStackFrame = parser->pc->topStackFrame->previousStackFrame;
    Interpreter::HeapPopStackFrame();
}

Value *Variable::VariableStringLiteralGet(Interpreter *pc, char *ident) {
    Value *lVal = NULL;

    if (MyTable::TableGet(&pc->stringLiteralTable, ident, &lVal, NULL, NULL, NULL))
        return lVal;
    else
        return NULL;
}

void Variable::VariableStringLiteralDefine(Interpreter *pc, char *ident, Value *val) {
    MyTable::TableSet(pc, &pc->stringLiteralTable, ident, val, NULL, 0, 0);
}

void *Variable::VariableDereferencePointer(Value *pointerValue, Value **derefVal,
    int *derefOffset, ValueType **derefType, int *derefIsLValue) {
    if (derefVal != NULL)
        *derefVal = NULL;

    if (derefType != NULL)
        *derefType = pointerValue->typ->fromType;

    if (derefOffset != NULL)
        *derefOffset = 0;

    if (derefIsLValue != NULL)
        *derefIsLValue = true;

    return pointerValue->val->pointer;
}

